﻿using System;
using System.Collections.Generic;
using System.Text;
using rkShared.Net;
using rkShared;
using System.Timers;
using System.Net.Sockets;
using rkShared.BEncode;

namespace rkServer
{
    public class ServerLogic
    {
        //used to synronize over collections..
        private static object sm_syncObjectPlayerFires;
        private static object sm_syncObjectPlayerMoves;


        private GameServer m_gameServer;
        /// <summary>
        /// Hold the player states...
        /// </summary>
        private Dictionary<ClientInfo, Player> m_playerStates;
        private Dictionary<ClientInfo, List<Vector3>> m_playerFires;
        private Dictionary<ClientInfo, Vector3> m_playerMoves;

        /// <summary>
        /// Timer will tick for each snapshot period.
        /// </summary>
        private Timer m_snaphotTimer;

        private const double SNAPSHOT_PERIOD = 40.0;    //25 times per second...

        public delegate void GameInfoEventHandler(ClientInfo client);
        public event GameInfoEventHandler ClientHit;

        private GameInfo m_gameInfo;
        static ServerLogic()
        {
            sm_syncObjectPlayerFires = new object();
            sm_syncObjectPlayerMoves = new object();
        }
        public ServerLogic(GameServer server, string gameName, int seed)
        {
            m_gameInfo = new GameInfo();
            m_gameInfo.Name = gameName;
            m_gameInfo.Seed = seed;
            m_gameInfo.ServerID = server.m_serverGuid;

            m_playerStates = new Dictionary<ClientInfo, Player>();
            m_playerFires = new Dictionary<ClientInfo, List<Vector3>>();
            m_playerMoves = new Dictionary<ClientInfo, Vector3>();

            m_snaphotTimer = new Timer(SNAPSHOT_PERIOD);
            m_snaphotTimer.Elapsed += new ElapsedEventHandler(m_snaphotTimer_Elapsed);
            m_snaphotTimer.AutoReset = true;
            m_snaphotTimer.Start();

            m_gameServer = server;
            m_gameServer.ClientRegistered += new rkShared.Net.ClientRegisteredEventHandler(m_gameServer_ClientRegistered);
            m_gameServer.GameMessageReceived += new MessageReceivedEventHandler(m_gameServer_GameMessageReceived);
            m_gameServer.ClientDisconnected += new GameServer.ClientDisconnectedEventHandler(m_gameServer_ClientDisconnected);
            m_gameServer.ClientEnteredGame += new ClientEnteredGameEventHandler(m_gameServer_ClientEnteredGame);
        }

        void m_gameServer_ClientEnteredGame(ClientInfo cliInfo, NetMessage msg)
        {
            this.SavePlayerState(cliInfo, msg.Data as Player);
            this.SendSnapshots(cliInfo);
        }

        void m_gameServer_ClientDisconnected(ClientInfo client, DisconnectReason reason)
        {
            this.RemoveClientState(client);
            this.SendSnapshots(null);

            NetMessage _msg = new NetMessage();
            _msg.MessageType = MessageType.Disconnect;
            _msg.Data = client;
            this.SendMessageToClients(_msg, ProtocolType.Tcp, client);
        }

        void m_snaphotTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            this.SendPlayerMoves();
            this.SendPlayerFires();
        }
        private void SendPlayerMoves()
        {
            if (m_playerMoves != null)
            {
                lock (sm_syncObjectPlayerMoves)
                {
                    foreach (KeyValuePair<ClientInfo, Vector3> move in m_playerMoves)
                    {
                        NetMessage _msg = new NetMessage();
                        _msg.ClientID = move.Key.ClientID;
                        _msg.MessageType = MessageType.Move;
                        _msg.Data = move.Value;

                        this.SendMessageToClients(_msg, ProtocolType.Udp, move.Key);
                    }
                    m_playerMoves.Clear();
                }
            }
        }
        private void SendPlayerFires()
        {
            if (m_playerFires != null)
            {
                lock (sm_syncObjectPlayerFires)
                {
                    foreach (KeyValuePair<ClientInfo, List<Vector3>> fires in m_playerFires)
                    {
                        foreach (Vector3 fire in fires.Value)
                        {
                            NetMessage _msg = new NetMessage();
                            _msg.ClientID = fires.Key.ClientID;
                            _msg.MessageType = MessageType.Fire;
                            _msg.Data = fire;

                            this.SendMessageToClients(_msg, ProtocolType.Udp, fires.Key);
                        }
                    }
                    m_playerFires.Clear();
                }
            }
        }
        /// <summary>
        /// Sends snapshots to each client except the given one.
        /// </summary>
        /// <param name="except"></param>
        private void SendSnapshots(ClientInfo except)
        {
            lock (m_gameServer.SyncObject)
            {
                try
                {
                    foreach (ClientInfo client in m_gameServer.Clients)
                    {
                        if (except != null && except == client)
                            continue;
                        this.SendSnapshotToClient(client);
                    }
                }
                catch { }
            }

        }
        private void SendSnapshotToClient(ClientInfo client)
        {
            Snapshot _s = this.PrepareSnapshot();
            NetMessage _msg = new NetMessage();
            _msg.MessageType = MessageType.Snapshot;

            if (m_playerStates.ContainsKey(client))
            {
                Player p = m_playerStates[client];
                if (p != null)
                {
                    //Don't send the client it's playerInfo...
                    _s.Players.Remove(p);
                }
            }
            _msg.Data = _s;
            m_gameServer.SendUdpMessage(client, _msg);
        }
        private Snapshot PrepareSnapshot()
        {
            Snapshot _result = new Snapshot();
            foreach (Player p in m_playerStates.Values)
            {
                _result.Players.Add(p);
            }
            return _result;
        }

        private void m_gameServer_GameMessageReceived(ClientInfo client, NetMessage msg)
        {
            switch (msg.MessageType)
            {
                case MessageType.Move:
                    this.SaveMove(client, msg.Data as Vector3);
                    break;
                case MessageType.Fire:
                    this.SaveFire(client, msg.Data as Vector3);
                    break;
                case MessageType.Dead:
                    msg.Data = new BEncodedString(msg.ClientID.ToString());
                    msg.ClientID = Guid.Empty;
                    this.SendMessageToClients(msg, ProtocolType.Udp, client);
                    break;
                case MessageType.Hit:
                    this.SendHit(msg);
                    break;
                case MessageType.ClientEnteredGame:
                    this.SavePlayerState(m_gameServer.FindClient(msg.ClientID), msg.Data as Player);
                    this.SendPlayers(client, msg);
                    //this.SendSnapshots(client);
                    break;
                case MessageType.Killed:
                case MessageType.Spawn:
                default:
                    this.SendMessageToClients(msg, ProtocolType.Udp, client);
                    break;
            }
        }

        private void SendHit(NetMessage msg)
        {
            NetMessage _msg = new NetMessage();
            Guid _clientID = new Guid((msg.Data as BEncodedString).ValueStr);//the client which get hit.
            ClientInfo _to = m_gameServer.FindClient(_clientID);

            if (this.ClientHit != null)
                this.ClientHit(_to);

            _msg.Data = new BEncodedString(msg.ClientID.ToString());
            _msg.MessageType = MessageType.Hit;

            if (_to != null)
            {
                m_gameServer.SendMessage(_to, _msg, ProtocolType.Udp);
            }
        }
        private void SaveFire(ClientInfo client, Vector3 vector3)
        {
            if (!m_playerFires.ContainsKey(client))
                m_playerFires[client] = new List<Vector3>();
            m_playerFires[client].Add(vector3);
        }

        private void SaveMove(ClientInfo client, Vector3 vector3)
        {
            lock (sm_syncObjectPlayerMoves)
            {
                m_playerMoves[client] = vector3;
            }
        }
        private void SendPlayers(ClientInfo client, NetMessage msg)
        {
            lock (sm_syncObjectPlayerFires)
            {
                this.SendMessageToClients(msg, ProtocolType.Udp, client);
            }
        }
        private void RemoveClientState(ClientInfo client)
        {
            if (m_playerStates.ContainsKey(client))
                m_playerStates.Remove(client);
        }
        private void SavePlayerState(ClientInfo client, Player player)
        {
            if (player != null)
            {
                player.ClientID = client.ClientID;
                m_playerStates[client] = player;
            }
        }
        void m_gameServer_ClientRegistered(rkShared.Net.ClientInfo cliInfo)
        {
            //Send the snapshot to newly connected client.
            this.SendSnapshotToClient(cliInfo);
            this.SendGameInfoToClient(cliInfo);
            //Send other players the connected client info..
            this.SendNewPlayerToClients(cliInfo);
        }
        private void SendGameInfoToClient(ClientInfo cliInfo)
        {
            NetMessage _msg = new NetMessage();
            _msg.MessageType = MessageType.GameInfo;
            _msg.Data = m_gameInfo;

            m_gameServer.SendMessage(cliInfo, _msg, ProtocolType.Tcp);
        }
        private void SendNewPlayerToClients(ClientInfo newPlayer)
        {
            NetMessage _msg = new NetMessage();
            _msg.MessageType = MessageType.Register;
            _msg.Data = newPlayer;

            this.SendMessageToClients(_msg, ProtocolType.Tcp, newPlayer);
        }

        /// <summary>
        /// Sends the message to all clients except the one provided.
        /// </summary>
        /// <param name="msg">The message to send to clients</param>
        /// <param name="protocol">UDP or TCP</param>
        /// <param name="except">The client to exclude, may be null</param>
        private void SendMessageToClients(NetMessage msg, ProtocolType protocol, ClientInfo except)
        {
            lock (m_gameServer.SyncObject)
            {
                foreach (ClientInfo client in m_gameServer.Clients)
                {
                    if (except != null && client == except)
                        continue;
                    m_gameServer.SendMessage(client, msg, protocol);
                }
            }
        }
        internal void EndServer()
        {
            NetMessage msg = new NetMessage();
            msg.MessageType = MessageType.Disconnect;
            msg.Data = new BEncodedString("GOODBYE CLIENTS");

            this.SendMessageToClients(msg, ProtocolType.Tcp, null);
            m_gameServer.Dispose();
        }
    }
}
